package com.czl.myRabbitMq.mqServer;

import com.czl.myRabbitMq.commom.Consume;
import com.czl.myRabbitMq.commom.exception.mqException;
import com.czl.myRabbitMq.mqServer.core.*;
import com.czl.myRabbitMq.mqServer.dataCenter.ConsumeManager;
import com.czl.myRabbitMq.mqServer.dataCenter.DiskDataManager;
import com.czl.myRabbitMq.mqServer.dataCenter.MemoryDataManager;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 虚拟主机类 本项目为了简化 只有一个虚拟主机 同时一个虚拟主机底下
 * 管理着很多 交换机和队列和消息 同时本类来提供一组核心API供其他类
 * 来进行访问和调用
 */
@Slf4j
@Getter
public class VirtualHost {
    private final String virtualHostName;
    //转发 和 匹配路径的类
    private final Router router = new Router();
    //代码中设计到交换机且需要上锁的操作
    private final Object exchangeLocker = new Object();
    private final Object queueLocker = new Object();
    private final DiskDataManager diskDataManager = new DiskDataManager();
    private final MemoryDataManager memoryDataManager = new MemoryDataManager();
    //管理消费的类
    private final ConsumeManager consumeManager = new ConsumeManager(this);



    public VirtualHost(String virtualHostName){
        //这里对虚拟主机进行初始化
        diskDataManager.init();
        this.virtualHostName = virtualHostName;
        //同时初始化过后, 因为服务器随时可能中断 所以需要对硬盘中的数据恢复到内存中
        memoryDataManager.recoverData(diskDataManager);
        log.info("恢复硬盘数据到内存成功");
    }

    //要是有交换机就返回true 要是没有对应的交换机则进行创建
    //同时这里添加一条命名规则 为了确认交换机和虚拟主机的对应关系
    //我们在每个交换机id前加上当前虚拟主机id作为前缀
    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean isDurable,
                                   boolean isAutoDelete, Map<String, Object> params){

        String virtualExchangeName = virtualHostName + "_" + exchangeName;
        //要是当前交换机已经存在则直接返回true
        synchronized (exchangeLocker) {
            if (memoryDataManager.selectExchange(exchangeName) != null) {
                return true;
            }
            //要是不存在才进行交换机的创建
            Exchange exchange = new Exchange();
            exchange.setName(virtualExchangeName);
            exchange.setExchangeType(exchangeType);
            exchange.setDurable(isDurable);
            exchange.setAutoDelete(isAutoDelete);
            exchange.setParams(params);
            //交换机创建完成之后 判断是够可持久存储属性Durable来判断是够写入硬盘
            try {

                if (isDurable)
                    diskDataManager.insertExchange(exchange);

                //插入内存
                memoryDataManager.insertExchange(exchange);
                return true;
            } catch (Exception e) {
                log.info(Arrays.toString(e.getStackTrace()));
                return false;
            }
        }
    }

    //删除交换机
    public boolean deleteExchange(String exchangeName){

        String virtualExchangeName = virtualHostName + "_" + exchangeName;

        //先查询到相应的交换机
        try {
            Exchange exchange = memoryDataManager.selectExchange(virtualExchangeName);
            if (exchange == null) {
                log.info("删除的交换机不存在" + exchangeName);
                return false;
            }
            //如果是可持久存储 则需要对硬盘上的内容也进行删除
            if (exchange.isDurable())
                diskDataManager.deleteExchange(virtualExchangeName);

            memoryDataManager.deleteExchange(virtualExchangeName);
            log.info("交换机删除成功");
            return true;
        }catch (Exception e){
            log.info(Arrays.toString(e.getStackTrace()));
            return false;
            }
        }

        //在交换机中声明一个队列 和上述声明交换机的方法相同
        public boolean queueDeclare(String queueName,boolean isDurable, boolean exclusive,
                                    boolean isAutoDelete,Map<String, Object> params){
        String virtualQueueName = virtualHostName + "_" + queueName;
            synchronized (queueLocker) {
        try {
                MSGQueue queue = memoryDataManager.selectQueue(virtualQueueName);
                if (queue != null) {
                    log.info("交换机已存在");
                    return true;
                }
                queue = new MSGQueue();
                queue.setName(virtualQueueName);
                queue.setDurable(isDurable);
                queue.setExclusive(exclusive);
                queue.setAutoDelete(isAutoDelete);
                queue.setParams(params);

                if (isDurable)
                    diskDataManager.insertQueue(queue);

                memoryDataManager.insertQueue(queue);
                log.info("队列创建成功" + virtualQueueName);
                return true;
            }catch(Exception e){
                log.info(Arrays.toString(e.getStackTrace()));
                return false;
            }
        }
    }

    public boolean deleteQueue(String queueName){
        String virtualQueueName = virtualHostName + "_" + queueName;
        try {
            MSGQueue queue = memoryDataManager.selectQueue(virtualQueueName);
            if(queue == null){
                log.info("要删除的队列不存在" + queueName);
                return false;
            }
            if (queue.isDurable())
                diskDataManager.deleteQueue(virtualQueueName);
            memoryDataManager.deleteQueue(virtualQueueName);
            log.info("删除成功");
            return true;
        }catch (Exception e){
            log.info(Arrays.toString(e.getStackTrace()));
            return false;
        }
    }

    //为交换机和队列创建一个绑定
    public boolean queueBinding(String exchangeName, String queueName, String bindingKey) {
        String virtualExchangeName = virtualHostName + "_" + exchangeName;
        String virtualQueueName = virtualHostName + "_" + queueName;
        synchronized (exchangeLocker) {
            synchronized (queueLocker) {
                try {

                    Binding binding = memoryDataManager.selectBinding(virtualExchangeName, virtualQueueName);
                    if (binding != null) {
                        log.info("绑定已存在" + virtualExchangeName + "_" + virtualQueueName);
                        return false;
                    }
                    binding = new Binding();
                    //创建绑定
                    binding.setExchangeName(virtualExchangeName);
                    binding.setQueueName(virtualQueueName);
                    binding.setBindingKey(bindingKey);


                    //!11111111111111111111111111111111111111111111111111
                    if (!router.checkBindingKey(bindingKey))
                        throw new mqException("绑定字符串不合法");

                    //先查询内存中是否存在交换机和队列 要是不存在绑定也就无意义

                    MSGQueue queue = memoryDataManager.selectQueue(virtualQueueName);


                    if (queue == null)
                        throw new mqException("绑定的队列不存在");

                    Exchange exchange = memoryDataManager.selectExchange(virtualExchangeName);


                    if (exchange == null)
                        throw new mqException("绑定的交换机不存在");

                    //交换机和队列同时为可持久存储的时候 绑定才可持久存储 不然没有意义
                    if (queue.isDurable() && exchange.isDurable())
                        diskDataManager.insertBinding(binding);

                    memoryDataManager.insertBinding(binding);
                    log.info("绑定创建成功" + bindingKey);
                    return true;
                } catch (Exception e) {
                    log.info(Arrays.toString(e.getStackTrace()));
                    return false;
                }
            }
        }
    }

    /**
     * 这里的删除方法不需要对 是否存在交换机和队列进行判定 因为一旦判定
     * 要是提前把其中一个交换机或者队列删除了 那这个绑定就删不掉了
     */
    public boolean queueUnbinding(String exchangeName, String queueName) {
        String virtualExchangeName = virtualHostName + "_" + exchangeName;
        String virtualQueueName = virtualHostName + "_" + queueName;
        synchronized (exchangeLocker) {
            synchronized (queueLocker) {
                try {
                    //先查询内存中绑定是否存在
                    Binding binding = memoryDataManager.selectBinding(virtualExchangeName, virtualQueueName);
                    if (binding == null) {
                        log.info("需要接触的绑定不存在" + exchangeName + "_" + queueName);
                        return false;
                    }
                    diskDataManager.deleteBinding(virtualExchangeName, virtualQueueName);

                    memoryDataManager.deleteBinding(binding);
                    log.info("删除绑定成功");
                    return true;
                } catch (Exception e) {
                    log.info(Arrays.toString(e.getStackTrace()));
                    return false;
                }
            }
        }
    }

    //发送消息到交换机中 交换机通过交换机类型和绑定关系进行转发
    public boolean basicPublish(String exchangeName, String routineKey,
                                ExchangeType exchangeType, BasicProperties basicProperties,
                                byte[] body){
        try{
            String virtualExchangeName = virtualHostName + "_" + exchangeName;
            //先在内存中查询出相应的交换机
            Exchange exchange = memoryDataManager.selectExchange(virtualExchangeName);
            if(exchange == null) {
                throw new mqException("要发送的交换机不存在" + exchangeName);
            }
            if(!router.checkRoutineKey(routineKey)) {
                throw new mqException("发送的routineKey格式错误" + routineKey);
            }

            //这里是直接交换机
            if(exchangeType == ExchangeType.DIRECT){
                //直接交换机这里的routineKey是队列名字 无需绑定关系
                //交换机直接发送给routineKey所指定的队列

                //先通过routineKey查询出相应的队列
                String virtualQueueName = virtualHostName + "_" + routineKey;
                MSGQueue queue = memoryDataManager.selectQueue(virtualQueueName);
                if(queue == null){
                    throw new mqException("routineKey标注的队列不存在" + routineKey);
                }
                Message message = Message.createMessage(routineKey,basicProperties,body);
                sendMessage(queue,message);
                log.info("发送消息"+message.getMessageId()+"到队列"+queue.getName()+"成功");

            } else {//因为本项目实现了3种类型的交换机 后2中交换机的转发规则类似所以可以合并写
                //扇出交换机 和 主题交换机
                //先获取交换机和队列之间的绑定关系
                ConcurrentHashMap<String, Binding> map = memoryDataManager.selectBindings(virtualExchangeName);
                //这里需要先对map进行一个遍历 同时遍历的过程中 entry元素为一个一个的键值对
                for(Map.Entry<String, Binding> entry : map.entrySet()){
                    //获取到每一个绑定
                    Binding binding = entry.getValue();
                    MSGQueue queue = memoryDataManager.selectQueue(binding.getQueueName());
                    if(queue == null){
                        //这是要是相应的队列不存在 我们不进行异常抛出
                        log.info("转发的交换机不存在" + binding.getQueueName());
                        //因为抛出了异常会影响整个程序的执行
                        continue;
                    }
                    //构造消息对象
                    Message message = Message.createMessage(routineKey,basicProperties,body);
                    //同时我们对主题交换的转发原则进行一个判断
                    if(!router.route(exchange.getExchangeType(),binding,message)){
                        //要是类型是主题交换机且不匹配转发规则的话 我们也不对其进行一个转发
                        continue;
                    }
                    //转发消息的核心逻辑
                    sendMessage(queue,message);
                    log.info("转发消息成功" + binding.getQueueName());
                }
            }
            return true;
        }catch (Exception e){
            log.info("发送消息失败");
            e.printStackTrace();
            return false;
        }
    }

    private void sendMessage(MSGQueue queue, Message message) throws InterruptedException {

        if(message.getDurable() == 1){//1表示可持久化存储 2表示不可持久化存储
            diskDataManager.writeMessage(message, queue);
        }
        memoryDataManager.sendMessage(queue,message);
        //队列新增消息之后 需要将队列名 告诉队列 以便消费者类方便对其的调用
        consumeManager.notifyConsume(queue.getName());
    }

    //表示为一个队列增加一个消费者
    //第一个参数表示 消费者标签， 第二个参数表示队列名
    //第三个参数表示是否自动返回自动应答 第四个参数表示传入消息的回调函数
    public boolean basicConsume(String consumeTag, String queueName, boolean autoACK, Consume consume){
        String virtualQueueName = virtualHostName + "_" + queueName;
        try{

            consumeManager.addConsume(consumeTag, virtualQueueName, autoACK, consume);

            log.info("队列" + queueName + "添加消费者成功" + consumeTag);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            log.info("队列" + queueName + "添加消费者成功" + consumeTag);
            return false;
        }
    }

    //消费者autoACK = false的情况下 需要手动调用该方法来对消息进行一个删除
    public boolean basicACK(String queueName, String messageId) {
        String virtualQueueName = virtualHostName + "_" + queueName;
        //先找出对应的队列和消息
        try {
            MSGQueue queue = memoryDataManager.selectQueue(virtualQueueName);
            if (queue == null) {
                throw new mqException("要删除消息的队列不存在" + queueName);
            }

            Message message = memoryDataManager.selectMessage(messageId);
            if (message == null){
                throw new mqException("要删除的消息不存在" + messageId);
            }
            //删除硬盘
            if(message.getDurable() == 1){
                diskDataManager.deleteMessage(message, queue);
            }
            //删除数据中心
            memoryDataManager.deleteMessage(messageId);
            //删除等待队列中的消息
            memoryDataManager.deleteMessageWaitACK(virtualQueueName,messageId);

            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

}
